//
// 用户客Websocket客户端连接接待
//

package member

import (
	"adai.design/jarvis/common/log"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/websocket"
	"strings"
	"time"
)

const (
	idleTimeout   = time.Second * 16
	verifyTimeout = time.Second * 5
)

type reception struct {
	conn    *websocket.Conn
	account *Account
	client  *Client

	send chan *Message
	read chan *Message
	task chan *Message
}

func (r *reception) writeMessage(msg *Message) error {
	if r.send != nil {
		r.send <- msg
		return nil
	}

	buf, _ := json.Marshal(msg)

	err := r.conn.WriteMessage(websocket.BinaryMessage, buf)
	return err
}

func (r *reception) readMessage() (*Message, error) {
	t, data, err := r.conn.ReadMessage()
	if err != nil {
		return nil, err
	}
	if t != websocket.BinaryMessage {
		return nil, errors.New("websocket message format err")
	}
	var message Message
	err = json.Unmarshal(data, &message)
	if err != nil {
		return nil, err
	}
	return &message, nil
}

// 客户端登录认证
func (r *reception) verify() error {
	r.conn.SetReadDeadline(time.Now().Add(verifyTimeout))
	msg, err := r.readMessage()
	if err != nil {
		return err
	}

	if msg.Path != msgPathLogin || msg.Method != MsgMethodPost {
		return errors.New("invalid login message type")
	}

	account, client, err := login(msg.Data)

	// 登录失败
	if err != nil {
		msg = &Message{
			Path:   msg.Path,
			Method: msg.Method,
			State:  fmt.Sprintf("%s", err),
		}
		r.writeMessage(msg)
		return err
	}

	r.account = account
	r.client = client

	ack := loginResponse{
		Id:    account.Id,
		Token: client.Session,
		Name:  account.Name,
		Icon:  account.Icon,
		Home:  account.Homes,
		Date:  client.Last.Format("2006-01-02 15:04:05"),
	}

	msg = &Message{
		Path:   msgPathLogin,
		Method: msg.Method,
		State:  "ok",
	}
	msg.Data, _ = json.Marshal(ack)
	r.writeMessage(msg)
	return nil
}

// 客户端登出
func (r *reception) logout(state string) {
	data := map[string]interface{}{
		"id":    r.account.Id,
		"data":  time.Now().Format("2006-01-02 15:04:05"),
		"dev_t": r.client.DevType,
	}
	msg := &Message{
		Path:   msgPathLogout,
		Method: MsgMethodPut,
		State:  state,
	}
	msg.Data, _ = json.Marshal(data)
	buf, _ := json.Marshal(msg)
	r.conn.WriteMessage(websocket.BinaryMessage, buf)
	r.conn.Close()
}

// 循环读取消息
func (r *reception) readLoop() {
	defer close(r.read)
	for {
		r.conn.SetReadDeadline(time.Now().Add(idleTimeout))
		msg, err := r.readMessage()
		if err != nil {
			log.InfoFrom("(%s:%s) read <<< %s", r.account.Account(), r.client.DevType, err)
			r.conn.Close()
			return
		}
		log.InfoFrom("(%s:%s) read >>> %s", r.account.Account(), r.client.DevType, msg)
		r.read <- msg
	}
}

// 循环发送消息
func (r *reception) sendLoop() {
	for msg := range r.send {
		r.conn.SetWriteDeadline(time.Now().Add(idleTimeout))
		buf, _ := json.Marshal(msg)
		log.InfoTo("(%s:%s) send >>> %s", r.account.Account(), r.client.DevType, string(buf))
		if err := r.conn.WriteMessage(websocket.BinaryMessage, buf); err != nil {
			r.conn.Close()
			return
		}
	}
}

// 客户端消息调度任务
func (r *reception) start(ws *receptions) {
	log.Trace("member client: %s arrive", r.conn.RemoteAddr())
	defer log.Trace("member account: %s leave", r.conn.RemoteAddr())
	defer r.conn.Close()

	err := r.verify()
	if err != nil {
		log.Error("member verify failed <-(%s) %s", r.conn.RemoteAddr(), err)
		return
	}

	r.read = make(chan *Message, 5)
	go r.readLoop()

	r.send = make(chan *Message, 5)
	go r.sendLoop()

	r.task = make(chan *Message, 5)

	ws.online <- r
	defer func() {
		ws.offline <- r
		close(r.task)
		close(r.send)
	}()

	r.conn.SetPingHandler(func(appData string) error {
		r.conn.SetReadDeadline(time.Now().Add(idleTimeout))
		r.conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(idleTimeout))
		return nil
	})

eventLoop:
	for {
		select {
		case msg, ok := <-r.read:
			if !ok {
				break eventLoop
			}

			if c, ok := memberRouter[msg.Path]; ok {
				c.handle(r, msg)
				continue
			}

			// 将消息发送给家庭中心处理
			if strings.Split(msg.Path, "/")[0] == "home" {
				pkg := &MessagePkg{
					Type: PkgTypeContext,
					Ctx: &Context{
						HomeId:   msg.Home,
						MemberId: r.account.Id,
						Session:  r.client.Session,
					},
					Msg: msg,
				}
				ws.rpkg <- pkg
			}

		// 向客户端发送消息
		case task, ok := <-r.task:
			if !ok {
				break eventLoop
			}
			r.writeMessage(task)
		}
	}
}
